﻿namespace Blaze.FX
{
    using System;
    using UnityEngine;

    /// <summary>
    /// 堆栈模糊算法。
    /// <remarks>http://incubator.quasimondo.com/processing/stackblur.pde</remarks>
    /// </summary>
    public class StackBlur
    {
        public StackBlur(int width, int height, int maxRadius = 20)
        {
            mWidth = width;
            mHeight = height;

            var wh = width*height;
            mR = new int[wh];
            mG = new int[wh];
            mB = new int[wh];

            mVmin = new int[Math.Max(width, height)];

            mMaxRadius = maxRadius;
            realloc();
        }

        /// <summary>
        /// 应用模糊效果。
        /// </summary>
        /// <param name="pixels">需要模糊的数据</param>
        /// <param name="radius">模糊半径</param>
        public void Apply(Color32[] pixels, int radius)
        {
            if (radius < 1)
                return;
            if (radius > mMaxRadius)
            {
                mMaxRadius = radius;
                realloc();
            }

            var width = mWidth;
            var height = mHeight;
            var wm = width - 1;
            var hm = height - 1;
            var div = radius + radius + 1;

            int rsum, gsum, bsum, x, y, i;
            int yi;

            var divsum = (div + 1) >> 1;
            divsum *= divsum;
            for (i = 0; i < 256*divsum; i++)
                mDv[i] = i/divsum;

            var yw = yi = 0;

            int stackpointer;
            int stackstart;
            Color32 sir;
            int rbs;
            var r1 = radius + 1;
            int routsum, goutsum, boutsum;
            int rinsum, ginsum, binsum;

            for (y = 0; y < height; y++)
            {
                rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
                Color32 p;
                for (i = -radius; i <= radius; i++)
                {
                    p = pixels[yi + Math.Min(wm, Math.Max(i, 0))];
                    sir = p;
                    mStack[i + radius] = sir;
                    rbs = r1 - Math.Abs(i);
                    rsum += sir.r*rbs;
                    gsum += sir.g*rbs;
                    bsum += sir.b*rbs;
                    if (i > 0)
                    {
                        rinsum += sir.r;
                        ginsum += sir.g;
                        binsum += sir.b;
                    }
                    else
                    {
                        routsum += sir.r;
                        goutsum += sir.g;
                        boutsum += sir.b;
                    }
                }
                stackpointer = radius;

                for (x = 0; x < width; x++)
                {
                    mR[yi] = mDv[rsum];
                    mG[yi] = mDv[gsum];
                    mB[yi] = mDv[bsum];

                    rsum -= routsum;
                    gsum -= goutsum;
                    bsum -= boutsum;

                    stackstart = stackpointer - radius + div;
                    sir = mStack[stackstart%div];

                    routsum -= sir.r;
                    goutsum -= sir.g;
                    boutsum -= sir.b;

                    if (y == 0)
                    {
                        mVmin[x] = Math.Min(x + radius + 1, wm);
                    }
                    p = pixels[yw + mVmin[x]];

                    mStack[stackstart%div] = p;

                    rinsum += p.r;
                    ginsum += p.g;
                    binsum += p.b;

                    rsum += rinsum;
                    gsum += ginsum;
                    bsum += binsum;

                    stackpointer = (stackpointer + 1)%div;
                    sir = mStack[stackpointer%div];

                    routsum += sir.r;
                    goutsum += sir.g;
                    boutsum += sir.b;

                    rinsum -= sir.r;
                    ginsum -= sir.g;
                    binsum -= sir.b;

                    yi++;
                }
                yw += width;
            }
            for (x = 0; x < width; x++)
            {
                rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
                var yp = -radius*width;
                for (i = -radius; i <= radius; i++)
                {
                    yi = Math.Max(0, yp) + x;

                    sir = new Color32((byte) mR[yi], (byte) mG[yi], (byte) mB[yi], 1);
                    mStack[i + radius] = sir;

                    rbs = r1 - Math.Abs(i);

                    rsum += mR[yi]*rbs;
                    gsum += mG[yi]*rbs;
                    bsum += mB[yi]*rbs;

                    if (i > 0)
                    {
                        rinsum += sir.r;
                        ginsum += sir.g;
                        binsum += sir.b;
                    }
                    else
                    {
                        routsum += sir.r;
                        goutsum += sir.g;
                        boutsum += sir.b;
                    }

                    if (i < hm)
                    {
                        yp += width;
                    }
                }
                yi = x;
                stackpointer = radius;
                for (y = 0; y < height; y++)
                {
                    pixels[yi] = new Color32((byte) mDv[rsum], (byte) mDv[gsum], (byte) mDv[bsum], 1);

                    rsum -= routsum;
                    gsum -= goutsum;
                    bsum -= boutsum;

                    stackstart = stackpointer - radius + div;
                    sir = mStack[stackstart%div];

                    routsum -= sir.r;
                    goutsum -= sir.g;
                    boutsum -= sir.b;

                    if (x == 0)
                    {
                        mVmin[y] = Math.Min(y + r1, hm)*width;
                    }
                    var tempP = x + mVmin[y];
                    sir = new Color32((byte) mR[tempP], (byte) mG[tempP], (byte) mB[tempP], 1);
                    mStack[stackstart%div] = sir;

                    rinsum += sir.r;
                    ginsum += sir.g;
                    binsum += sir.b;

                    rsum += rinsum;
                    gsum += ginsum;
                    bsum += binsum;

                    stackpointer = (stackpointer + 1)%div;
                    sir = mStack[stackpointer];

                    routsum += sir.r;
                    goutsum += sir.g;
                    boutsum += sir.b;

                    rinsum -= sir.r;
                    ginsum -= sir.g;
                    binsum -= sir.b;

                    yi += width;
                }
            }
        }

        private void realloc()
        {
            var div = mMaxRadius + mMaxRadius + 1;
            var divsum = (div + 1) >> 1;
            divsum *= divsum;
            mDv = new int[256*divsum];
            mStack = new Color32[div];
        }

        private readonly int[] mVmin;
        private int[] mDv;
        private Color32[] mStack;
        private readonly int mWidth;
        private readonly int mHeight;
        private readonly int[] mR, mG, mB;
        private int mMaxRadius;
    }
}